package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"time"
)

// 设备类型枚举
type DeviceType int

const (
	PCS DeviceType = iota
	BMS
	METER
	ENVIRONMENT
	AIR_CONDITIONER
	WATER_SENSOR
)

// 设备信息
type Device struct {
	ID          string
	Type        DeviceType
	Name        string
	Address     uint16
	Description string
}

// 储能柜信息
type Cabinet struct {
	ID       string
	Name     string
	Devices  []Device
	Location string
}

// 储能站点信息
type EnergyStorageSite struct {
	ID          string
	Name        string
	Cabinets    []Cabinet
	MeterPoints []MeterPoint
}

// 电表点位
type MeterPoint struct {
	ID      string
	Name    string
	Type    string
	Address uint16
	CTRatio float64
	VTRatio float64
}

// 生成设备地址
func GenerateDeviceAddress(cabinetIndex int, deviceType DeviceType, deviceIndex int) uint16 {
	base := uint16(deviceType) << 12
	cabinetOffset := uint16(cabinetIndex) << 8
	deviceOffset := uint16(deviceIndex)
	return base + cabinetOffset + deviceOffset
}

// 示例: 初始化站点
func InitSampleSite() *EnergyStorageSite {
	site := &EnergyStorageSite{
		ID:   "ESS001",
		Name: "示范储能电站",
	}

	// 添加储能柜
	cabinet1 := Cabinet{
		ID:   "CAB001",
		Name: "1号储能柜",
	}

	// 添加PCS设备
	cabinet1.Devices = append(cabinet1.Devices, Device{
		ID:          "PCS001",
		Type:        PCS,
		Name:        "1号PCS",
		Address:     GenerateDeviceAddress(0, PCS, 0),
		Description: "储能变流器",
	})

	// 添加BMS设备
	cabinet1.Devices = append(cabinet1.Devices, Device{
		ID:          "BMS001",
		Type:        BMS,
		Name:        "1号BMS",
		Address:     GenerateDeviceAddress(0, BMS, 0),
		Description: "电池管理系统",
	})

	// 添加电表
	site.MeterPoints = append(site.MeterPoints, MeterPoint{
		ID:      "MTR001",
		Name:    "储能计量电表",
		Type:    "储能计量",
		Address: 0x3000,
		CTRatio: 2000,
		VTRatio: 100,
	})

	site.Cabinets = append(site.Cabinets, cabinet1)
	return site
}

// 主程序
func main() {
	// 初始化站点
	site := InitSampleSite()
	log.Printf("初始化站点: %s (%s)", site.Name, site.ID)

	// 连接IEC104从站
	master, err := NewMaster("127.0.0.1:2404", 0x01, 10*time.Second,
		"client.crt",
		"client.key",
		"ca.crt")
	if err != nil {
		log.Fatal("连接从站失败: ", err)
	}
	defer master.conn.Close()

	// 发送总召唤
	err = master.SendGeneralInterrogation()
	if err != nil {
		log.Fatal("发送总召唤失败: ", err)
	}

	// 持续读取数据
	for {
		asdus, err := master.ReadData()
		if err != nil {
			log.Println("读取数据错误: ", err)
			continue
		}

		for _, asdu := range asdus {
			ProcessASDU(site, asdu)
		}
	}
}

// 处理ASDU数据
func ProcessASDU(site *EnergyStorageSite, asdu ASDU) {
	// 根据ASDU类型处理数据
	switch asdu.TypeID {
	case 0x0D: // 测量值,归一化值
		// 解析数据并更新站点状态
	case 0x1E: // 带时标的测量值
		// 解析带时标的数据
	}
}

// Master 表示IEC104主站
type Master struct {
	conn    net.Conn
	address uint16 // 公共地址
	timeout time.Duration
}

// NewMaster 创建一个新的IEC104主站连接
// 修改NewMaster函数，添加TLS配置参数
func NewMaster(addr string, commonAddr uint16, timeout time.Duration, certFile, keyFile, caFile string) (*Master, error) {
	// 加载证书和私钥
	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return nil, fmt.Errorf("加载证书失败: %v", err)
	}

	// 加载CA证书
	caCert, err := ioutil.ReadFile(caFile)
	if err != nil {
		return nil, fmt.Errorf("加载CA证书失败: %v", err)
	}
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	// 配置TLS
	tlsConfig := &tls.Config{
		Certificates:       []tls.Certificate{cert},
		RootCAs:            caCertPool,
		MinVersion:         tls.VersionTLS13,
		InsecureSkipVerify: true, // 跳过主机名验证(仅用于测试环境)
	}

	// 创建TLS连接
	conn, err := tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", addr, tlsConfig)
	if err != nil {
		return nil, err
	}

	return &Master{
		conn:    conn,
		address: commonAddr,
		timeout: timeout,
	}, nil
}

// SendGeneralInterrogation 发送总召唤命令(C_IC_NA_1)
func (m *Master) SendGeneralInterrogation() error {
	// 构建ASDU报文
	asdu := make([]byte, 6)
	asdu[0] = 0x64 // 类型标识符: C_IC_NA_1 (100)
	asdu[1] = 0x01 // 可变结构限定词: SQ=0, Number=1
	asdu[2] = 0x06 // 传送原因: 激活(6)
	asdu[3] = 0x00 // 公共地址低字节
	asdu[4] = 0x00 // 公共地址高字节
	asdu[5] = 0x00 // 信息体地址(总召唤固定为0)

	// 构建I帧报文头
	frame := make([]byte, 6)
	frame[0] = 0x68 // 启动字符
	frame[1] = 0x04 // 长度
	frame[2] = 0x00 // 发送序列号低字节
	frame[3] = 0x00 // 发送序列号高字节
	frame[4] = 0x00 // 接收序列号低字节
	frame[5] = 0x00 // 接收序列号高字节

	// 组合完整报文
	frame = append(frame, asdu...)

	// 设置写超时
	if err := m.conn.SetWriteDeadline(time.Now().Add(m.timeout)); err != nil {
		return err
	}

	// 发送报文
	_, err := m.conn.Write(frame)
	return err
}

// ReadData 从连接读取IEC104数据帧
func (m *Master) ReadData() ([]ASDU, error) {
	// 设置读超时
	if err := m.conn.SetReadDeadline(time.Now().Add(m.timeout)); err != nil {
		return nil, err
	}

	// 读取报文头(6字节)
	header := make([]byte, 6)
	if _, err := io.ReadFull(m.conn, header); err != nil {
		return nil, err
	}

	// 验证启动字符
	if header[0] != 0x68 {
		return nil, fmt.Errorf("invalid start byte: %02X", header[0])
	}

	// 获取ASDU长度
	length := int(header[1])
	if length < 4 {
		return nil, fmt.Errorf("invalid ASDU length: %d", length)
	}

	// 读取ASDU数据
	asduData := make([]byte, length)
	if _, err := io.ReadFull(m.conn, asduData); err != nil {
		return nil, err
	}

	// 解析ASDU
	asdu := ASDU{
		TypeID:     asduData[0],
		SQ:         asduData[1]&0x80 != 0,
		NumItems:   int(asduData[1] & 0x7F),
		Cause:      asduData[2],
		CommonAddr: uint16(asduData[3]) | uint16(asduData[4])<<8,
		Data:       asduData[5:],
	}

	return []ASDU{asdu}, nil
}

// ASDU 表示应用服务数据单元
type ASDU struct {
	TypeID     byte   // 类型标识符
	SQ         bool   // 顺序标志
	NumItems   int    // 信息体数量
	Cause      byte   // 传送原因
	CommonAddr uint16 // 公共地址
	Data       []byte // 信息体数据
}
